package br.ufpe.cin.msc.jrsj2.recommender.algorithm;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import br.ufpe.cin.msc.jrsj2.recommender.algorithm.cb.AttributeBased;
import br.ufpe.cin.msc.jrsj2.recommender.algorithm.cf.CorrelationBased;
import br.ufpe.cin.msc.jrsj2.recommender.algorithm.newuser.NewUserStrategy;
import br.ufpe.cin.msc.jrsj2.recommender.algorithm.newuser.NewUserStrategy.E_NewUserStrategy;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.domain.Client;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.domain.Profile;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.domain.Program;

public class Recommender {

	private Program currentProgram;
	private List<Integer> results;

	public enum Algorithm {
		CF_CORRELATION_BASED, CF_NAYVE_BAYES, CB_ATTRIBUTE_BASED
	};

	private Map<Algorithm, Class<? extends RecommendationAlgorithm>> algorithmMap;
	private final int numberOfItens;

	public Recommender(Program currentProgram, int length) {
		algorithmMap = new HashMap<Algorithm, Class<? extends RecommendationAlgorithm>>();
		this.currentProgram = currentProgram;
		loadAlgorithms();
		numberOfItens = length;
		results = new ArrayList<Integer>();
	}

	private void loadAlgorithms() {
		algorithmMap.put(Algorithm.CB_ATTRIBUTE_BASED, AttributeBased.class);
		algorithmMap
				.put(Algorithm.CF_CORRELATION_BASED, CorrelationBased.class);
		// algorithmMap.put(Algorithm.CF_NAYVE_BAYES, NayveBayesBased.class);
	}

	@SuppressWarnings("rawtypes")
	public List<Program> getRecommendations(List<Program> itens,
			List<Profile> activeProfileList, List<Program> blackList) {
		int remainingItens = numberOfItens;
		List<Program> recommendations = new ArrayList<Program>();

		if (!activeProfileList.isEmpty()) {
			Client activeClient = activeProfileList.get(0).getClient();
			Class[] classes = new Class[] { Client.class, List.class,
					List.class };
			Object[] args = new Object[] { activeClient, itens,
					activeProfileList };

			for (Class<? extends RecommendationAlgorithm> algorithm : algorithmMap
					.values()) {
				try {
					RecommendationAlgorithm recommendationAlgorithm = algorithm
							.getConstructor(classes).newInstance(args);
					List<Program> partialRecommendations = recommendationAlgorithm
							.getRecommendations(remainingItens);

					results.add(partialRecommendations.size());

					for (Program program : partialRecommendations)
						if (!recommendations.contains(program)
								&& !blackList.contains(program))
							recommendations.add(program);

					remainingItens -= partialRecommendations.size();

					if (remainingItens == 0)
						break;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		if (remainingItens != 0) {
			// int strategyIndex = new Random()
			// .nextInt(E_NewUserStrategy.values().length);
			int strategyIndex = 1;
			E_NewUserStrategy strategy = E_NewUserStrategy.class
					.getEnumConstants()[strategyIndex];

			NewUserStrategy.setNewUserStrategy(strategy);
			List<Program> partialRecommendations = NewUserStrategy
					.getStrategy().getItens(itens, remainingItens);

			for (Program program : partialRecommendations)
				if (!recommendations.contains(program))
					recommendations.add(program);
		}
		
		results.add(remainingItens);

		sortRecommendations(recommendations, activeProfileList);

		return recommendations;
	}
	
	public List<Integer> getResults() {
		return this.results;
	}

	public void sortRecommendations(List<Program> recommendations,
			List<Profile> activeProfileList) {
		boolean sortByRating = true;

		for (Program program : recommendations)
			if (program.getGenre().equals(currentProgram.getGenre())) {
				Collections.sort(recommendations, new Comparator<Program>() {
					@Override
					public int compare(Program p1, Program p2) {
						return p1.getGenre().compareTo(p2.getGenre());
					}
				});
				sortByRating = false;
				break;
			}

		if (sortByRating) {
			Collections.sort(activeProfileList, new Comparator<Profile>() {
				@Override
				public int compare(Profile p1, Profile p2) {
					return p1.getRating().compareTo(p2.getRating());
				}
			});

			final List<Profile> auxActiveProfileList = activeProfileList;

			Collections.sort(recommendations, new Comparator<Program>() {
				@Override
				public int compare(Program p1, Program p2) {
					for (Profile profile : auxActiveProfileList)
						if (p1.getGenre().equals(
								profile.getProgram().getGenre()))
							return p1.getGenre().compareTo(p2.getGenre());
					return 1;
				}
			});
		}

	}
}
